package com.github.permissiondog.cancanpbackend.service;

import com.github.permissiondog.cancanpbackend.Database;
import com.github.permissiondog.cancanpbackend.dao.*;
import com.github.permissiondog.cancanpbackend.model.*;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

public class ApplicationService {
    private ApplicationService() {

    }

    @Getter
    private static final ApplicationService instance = new ApplicationService();

    /**
     * 创建申请
     * @param applicantId   申请人 ID
     * @param templateId    模板 ID
     * @param answers       回答, key 为问题 ID, value 为回答
     * @return              创建好的申请
     */
    public Application createApplication(int applicantId, int templateId, Map<Integer, String> answers) {
        try (var session = Database.getInstance().openSession(false)) {
            var applicationDao = session.getMapper(ApplicationDao.class);
            var answerDao = session.getMapper(TableAnswerDao.class);
            var questionDao = session.getMapper(TableQuestionDao.class);

            var application = new Application();
            application.setApplicantId(applicantId);
            application.setTemplateId(templateId);
            application.setCreationTime(LocalDateTime.now());
            application.setConfirmed(false);
            applicationDao.insertApplication(application);

            answers.forEach((questionId, answerValue) -> {
                var question = questionDao.getQuestionById(questionId);
                if (question == null) {
                    throw new RuntimeException("无法找到问题 " + questionId);
                }

                if (question.getTemplateId() != templateId) {
                    throw new RuntimeException("问题 " + questionId + " 不属于模板 " + templateId);
                }

                var answer = new TableAnswer();
                answer.setAnswer(answerValue);
                answer.setApplicationId(application.getId());
                answer.setQuestionId(questionId);
                answerDao.createAnswer(answer);
            });

            session.commit();
            return application;
        }
    }

    /**
     * 同意申请
     * @param applicationId 申请 ID
     * @param applicantId   申请人 ID
     */
    public void confirmApplication(int applicationId, int applicantId) {
        try (var session = Database.getInstance().openSession()) {
            var applicationDao = session.getMapper(ApplicationDao.class);
            var progressDao = session.getMapper(ApplicationProgressDao.class);
            var app = applicationDao.getApplicationById(applicationId);
            if (app == null) {
                throw new RuntimeException("申请不存在");
            }
            if (app.getApplicantId() != applicantId) {
                throw new RuntimeException("申请人 ID 与申请发起人 ID 不一致");
            }
            if (app.isConfirmed()) {
                throw new RuntimeException("申请已被确认");
            }

            if (!isApplicationClosed(applicationId)) {
                throw new RuntimeException("审批还在进行中");
            }

            applicationDao.confirmApplication(applicationId, true);
        }
    }

    public ApplicationStatus getApplicationStatus(int applicationId) {
        var application = getApplicationById(applicationId);
        if (application == null) {
            throw new RuntimeException("申请不存在");
        }
        var template = TemplateService.getInstance().getTemplateById(application.getTemplateId());

        var approvers = TemplateService.getInstance().getApprovers(template.getId());
        var progress = getProgressByApplicationId(applicationId);

        if (approvers.size() == progress.size()) {
            return progress.get(progress.size() - 1).isApprove() ? ApplicationStatus.APPROVED : ApplicationStatus.REJECTED;
        } else if (progress.size() == 0) {
            return ApplicationStatus.PENDING;
        } else {
            return progress.get(progress.size() - 1).isApprove() ? ApplicationStatus.PENDING : ApplicationStatus.REJECTED;
        }
    }

    public boolean isApplicationClosed(int applicationId) {
        return switch (getApplicationStatus(applicationId)) {
            case APPROVED, REJECTED -> true;
            default -> false;
        };
    }

    /**
     * 获取申请当前审批人
     * @param applicationId 申请 ID
     * @return              返回审批人信息, 若审批已完成则返回 null
     */
    public User getCurrentApprover(int applicationId) {
        if (isApplicationClosed(applicationId)) {
            return null;
        }
        var app = getApplicationById(applicationId);
        var progress = getProgressByApplicationId(applicationId);
        var approvers = TemplateService.getInstance().getApprovers(app.getTemplateId());
        return UserService.getInstance().getUserById(approvers.get(progress.size()).getApproverId());
    }

    public TableAnswer getAnswer(int applicationId, int questionId) {
        try (var session = Database.getInstance().openSession()) {
            var answerDao = session.getMapper(TableAnswerDao.class);
            return answerDao.getAnswer(applicationId, questionId);
        }
    }

    /**
     * 获取申请
     * @param applicationId 申请 ID
     * @return              申请
     */
    public Application getApplicationById(int applicationId) {
        try (var session = Database.getInstance().openSession()) {
            var applicationDao = session.getMapper(ApplicationDao.class);
            return applicationDao.getApplicationById(applicationId);
        }
    }

    /**
     * 获取用户的所有申请
     * @param applicantId   申请人 ID
     * @return              申请列表
     */
    public List<Application> getApplications(int applicantId) {
        try (var session = Database.getInstance().openSession()) {
             var applicationDao = session.getMapper(ApplicationDao.class);
             return applicationDao.getApplicationsByApplicantId(applicantId);
        }
    }

    /**
     * 获取申请人可以审批的所有申请
     * @param approverId    申请人 ID
     * @return              申请列表
     */
    public List<Application> getApplicationsByApproverId(int approverId) {
        try (var session = Database.getInstance().openSession()) {
            var applicationDao = session.getMapper(ApplicationDao.class);
            return applicationDao.getApplicationsByApproverId(approverId);
        }
    }

    /**
     * 同意/拒绝审批
     * @param applicationId 审批申请 ID
     * @param approverId    审批人 ID
     * @param approve       是否同意
     * @param opinion       意见, 可为空
     * @return              成功返回创建好的审批进度
     */
    public ApplicationProgress approve(int applicationId, int approverId, boolean approve, String opinion) {
        var progress = new ApplicationProgress();
        progress.setApplicationId(applicationId);
        progress.setApprove(approve);
        progress.setApproverId(approverId);
        progress.setOpinion(opinion);
        progress.setSubmitTime(LocalDateTime.now());
        try (var session = Database.getInstance().openSession(false)) {
            var applicationDao = session.getMapper(ApplicationDao.class);
            var progressDao = session.getMapper(ApplicationProgressDao.class);
            var approverDao = session.getMapper(ApproverDao.class);

            var app = applicationDao.getApplicationById(applicationId);
            var approvers = approverDao.getApprovers(app.getTemplateId());
            var currentProgress = progressDao.getProgressByApplicationId(applicationId);
            if (approvers.size() <= currentProgress.size()
                || approvers.get(currentProgress.size()).getApproverId() != approverId) {
                throw new RuntimeException("未到该审批人审批");
            }
            progressDao.insertProgress(progress);
            session.commit();

            return getProgressById(progress.getId());
        }
    }

    public ApplicationProgress getProgressById(int progressId) {
        try (var session = Database.getInstance().openSession()) {
            var progressDao = session.getMapper(ApplicationProgressDao.class);
            return progressDao.getProgressById(progressId);
        }
    }

    public List<ApplicationProgress> getProgressByApplicationId(int applicationId) {
        try (var session = Database.getInstance().openSession()) {
            var progressDao = session.getMapper(ApplicationProgressDao.class);
            return progressDao.getProgressByApplicationId(applicationId);
        }
    }
}
